#!/bin/sh
#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.

#@function: backup oracle db and log
. "${SCRIPT_ROOT_PATH}/oracle_sbin_func.sh"

check_archive_log_lost()
{
    instance_name=$1
    db_user=$2
    db_password=$3
    from_scn=$4
    resetlogs_id=$5
    node_list=$6

    tmp_sql="${STMP_PATH}/get_database_node_list_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_database_node_rst_${PID}.txt"

    for node in ${node_list}; do
        echo "set linesize 999" > "${tmp_sql}"
        echo "COL name FORMAT a500" >> "${tmp_sql}"
        echo "select to_char(first_change#), to_char(next_change#), name  from v\$archived_log where name is not null \
        and name not like '/mnt/databackup/%' \
        and THREAD#=${node} and RESETLOGS_ID=${resetlogs_id} and (deleted = 'NO') and (ARCHIVED='YES') and (STATUS = 'A') \
        and next_change# > ${from_scn};" >> "${tmp_sql}"

        echo "exit" >> "${tmp_sql}"
        set_db_silence_sql "${tmp_sql}"
        oracle_exe_sql "${db_user}" "${db_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 30 1
        ret_code=$?
        if [ ${ret_code} -ne 0 ]; then
            log_error "CheckLogLost failed, ret=${ret_code}"
            delete_file "${tmp_sql}" "${tmp_rst}" "true"
            return "${ERROR_ORACLE_BACKUP_FAILED}"
        fi
        if [ -s "${tmp_rst}" ]; then
            first_change=`cat "${tmp_rst}" | head -1 | $MYAWK '{print $1}'`
            log_info "first_change=${first_change}, from_scn=${from_scn}."
            if [ "${first_change}" -gt "${from_scn}" ]; then
                cat "${tmp_rst}" | head -1 >> "${LOG_FILE_NAME}"
                log_error "node(${node}) have log lost, backup failed."
                first_change_rst="${STMP_PATH}/check_archive_log_first_change_${PID}.txt"
                echo "${first_change}" > "${first_change_rst}"
                delete_file "${tmp_sql}" "${tmp_rst}" "true"
                return "${ERROR_ORACLE_HAS_LOG_LOST}"
            fi
        fi
    done
    delete_file "${tmp_sql}" "${tmp_rst}" "true"
}

get_archive_log_num()
{
    instance_name=$1
    db_user=$2
    db_password=$3
    from_scn=$4
    archive_path=$5

    tmp_sql="${STMP_PATH}/get_archive_log_num_sql_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_archive_log_num_rst_${PID}.txt"

    echo "set linesize 999" > "${tmp_sql}"
    echo "select count(name) from v\$archived_log where name is not null and name not like '%${archive_path}%'  and next_change# > ${from_scn};" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"

    oracle_exe_sql "${db_user}" "${db_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 30 1
    ret_code=$?
    if [ ${ret_code} -ne 0 ]; then
        log_error "GetArchiveLogNum failed, ret=${ret_code}"
        delete_file "${tmp_sql}" "${tmp_rst}" "true"
        echo 2
        return 
    fi
    num=`cat "${tmp_rst}"`
    let num+=2
    delete_file "${tmp_sql}" "${tmp_rst}" "true"
    echo "${num}"
}

build_backup_archive_log_sql()
{
    tmp_sql=$1
    log_num=$2

    echo "configure backup optimization off;" > "${tmp_sql}"
    echo "configure controlfile autobackup off;" >> "${tmp_sql}"
    echo "set nocfau;" >> "${tmp_sql}"
    echo "configure maxsetsize to unlimited;" >> "${tmp_sql}"
    if [ "${IS_ENC_BK}" -eq 0 ] && [ "$ORA_PREVERSION" -gt 11 ]; then
        echo "configure encryption for database off;" >> "${tmp_sql}"
    fi

    echo "RUN" >> "${tmp_sql}"
    echo "{" >> "${tmp_sql}"
    echo "    SET COMMAND ID TO 'ProtectAgent_Archive_Backup';" >> "${tmp_sql}"

    if [ "${QOS}" -gt 0 ]; then
        if [ "${log_num}" -ge "${CHANNELS}" ] || [ "${log_num}" -eq 0 ]; then
            real_qos=`expr "${QOS}" \* 1024 / "${CHANNELS}"`
        else
            real_qos=`expr "${QOS}" \* 1024 / "${log_num}"`
        fi
    fi

    index=1
    if [ "${DBISCLUSTER}" -eq 1 ] && [ "${NODENUMBER}" -ne 0 ]; then
        service_names=`get_service_names "${DBINSTANCE}" "${DBUSER}" "${DBUSERPWD}"`
        service_names=`echo "${service_names}" | $MYAWK -F "," '{print $1}'`
        while [ "$index" -le "$CHANNELS" ]; do
            IPPORTINFO=`echo "$IPPORTINFO" | sed 's/;/ /g' ` 
            for instance_node_info in ${IPPORTINFO}; do
                host_info=`echo "${instance_node_info}" | $MYAWK -F "," '{print $2}'`
                port_info=`echo "${instance_node_info}" | $MYAWK -F "," '{print $3}'`
                sid_info=`echo "${instance_node_info}" | $MYAWK -F "," '{print $1}'`
                qos_sql="    allocate channel eBackup`printf "%02d" "$index"` type disk format '${ARCHIVE}/resetlogs_id${resetlogs_id}/%T_%U'"
                if [ "${QOS}" -eq 0 ]; then
                    echo "${qos_sql} connect /@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=${host_info})(Port=${port_info}))(CONNECT_DATA=(SERVICE_NAME=${service_names})));" >> "${tmp_sql}"
                else
                    echo "${qos_sql} rate `printf "%d" "${real_qos}"`k connect /@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=${host_info})(Port=${port_info}))(CONNECT_DATA=(SERVICE_NAME=${service_names})));" >> "${tmp_sql}"
                fi
                index=`expr "$index" + 1`
                if [ "${index}" -gt "${CHANNELS}" ]; then
                    break
                fi
            done
        done
    else
        while [ "$index" -le "$CHANNELS" ]; do
            qos_sql="    allocate channel eBackup`printf "%02d" "$index"` type disk format '${ARCHIVE}/resetlogs_id${resetlogs_id}/%T_%U'"
            if [ "${QOS}" -gt 0 ]; then
                echo "${qos_sql} rate `printf "%d" "${real_qos}"`k;" >> "${tmp_sql}"
            else
                echo "${qos_sql};" >> "${tmp_sql}"
            fi
            index=`expr "$index" + 1`
        done
    fi

    echo "    configure device type disk parallelism $CHANNELS;" >> "${tmp_sql}"
    if [ "${open_mode}" != "READONLY" ]  && [ "${open_mode}" != "READONLYWITHAPPLY" ] && [ "${open_mode}" != "MOUNTED" ];then
        echo "    sql 'alter system archive log current';" >> "${tmp_sql}"
    fi
    echo "    backup as copy archivelog from scn $from_scn format '${ARCHIVE}/resetlogs_id${resetlogs_id}/arch_%t_%s.log';" >> "${tmp_sql}"
    # Oracle 11G RAC 集群的情况下，备份控制文件 as copy 会报错，Oracle的BUG
    if [ "$ORA_PREVERSION" -lt 12 ] && [ "$DBISCLUSTER" -gt 0 ]; then
        if [ -z "$PDB_TAG" ]; then
            echo "    backup current controlfile format '${ARCHIVE}/resetlogs_id${resetlogs_id}/controlfile.ctl' tag 'EBACKUP-$DBNAME-CTL' reuse;" >> "${tmp_sql}"
        else
            echo "    backup current controlfile format '${ARCHIVE}/resetlogs_id${resetlogs_id}/controlfile.ctl' tag 'EBACKUP-$PARENT_NAME-$PDB_TAG-DATA';" >> "${tmp_sql}"
        fi
    else
        if [ -z "$PDB_TAG" ]; then
            echo "    backup as copy current controlfile format '${ARCHIVE}/resetlogs_id${resetlogs_id}/controlfile.ctl' tag 'EBACKUP-$DBNAME-CTL' reuse;" >> "${tmp_sql}"
        else
            echo "    backup as copy current controlfile format '${ARCHIVE}/resetlogs_id${resetlogs_id}/controlfile.ctl' tag 'EBACKUP-$PARENT_NAME-$PDB_TAG-DATA' reuse;" >> "${tmp_sql}"
        fi
    fi
    echo "}" >> "${tmp_sql}"
}

get_last_archive_log_next_change()
{
    instance_name=$1
    db_user=$2
    db_password=$3
    from_scn=$4
    archive_path=$5

    tmp_sql="${STMP_PATH}/get_last_archive_log_next_change_sql_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_last_archive_log_next_change_rst_${PID}.txt"

    echo "set linesize 999" > "${tmp_sql}"
    echo "select max(next_change#) from v\$archived_log where name is not null and name not like '%${archive_path}%'  and next_change# <= ${from_scn};" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"

    oracle_exe_sql "${db_user}" "${db_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 30 1
    ret_code=$?
    if [ ${ret_code} -ne 0 ]; then
        log_error "GetLastArchiveLogNextChange failed, ret=${ret_code}"
        delete_file "${tmp_sql}" "${tmp_rst}" "true"
        return "${ret_code}"
    fi
    next_change=`cat "${tmp_rst}"`
    delete_file "${tmp_sql}" "${tmp_rst}" "true"
    echo "${next_change}"
}



splicing_log_range()
{
    old_range=$1
    old_min_scn=`echo "${old_range}" | ${MYAWK} '{print $NF}' | ${MYAWK} -F "~" '{print $1}'`
    old_min_time=`echo "${old_range}" | ${MYAWK} '{print $NF}' | ${MYAWK} -F "~" '{print $2}'`
    old_max_scn=`echo "${old_range}" | ${MYAWK} '{print $NF}' | ${MYAWK} -F "~" '{print $3}'`
    new_range=$2
    new_min_scn=`echo "${new_range}" | ${MYAWK} '{print $1}' | ${MYAWK} -F "~" '{print $1}'`
    new_max_scn=`echo "${new_range}" | ${MYAWK} '{print $1}' | ${MYAWK} -F "~" '{print $3}'`
    new_max_time=`echo "${new_range}" | ${MYAWK} '{print $1}' | ${MYAWK} -F "~" '{print $4}'`

    if [ "${old_max_scn}" -eq "${new_min_scn}" ]; then
        rst=`echo "${old_range}" | ${MYAWK} '{for (i=1;i<NF;i++)printf("%s ", $i);print ""}'`
        rst="${rst}${old_min_scn}~${old_min_time}~${new_max_scn}~${new_max_time}"
        rst="${rst}`echo "${new_range}" | ${MYAWK} '{for (i=2;i<=NF;i++)printf(" %s", $i);print ""}'`"
    else
        rst="${old_range} ${new_range}"
    fi
    echo "${rst}"
}

get_archivelog_current_range()
{
    instance_name=$1
    db_user=$2
    db_password=$3

    if [ -f "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range ]; then
        mv "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range.bak
    else
        touch "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range.bak
    fi

    node_list=`get_database_node_list "${instance_name}" "${db_user}" "${db_password}"`
    ret_code=$?
    if [ $ret_code -ne 0 ]; then
        log_error "Get node list failed ret code $ret_code"
        return $ret_code
    fi
    # 全备和日志备份并发，全备会把log仓下的日志文件置为unavailable，所以这里改为不校验status
    LOG_IS_BACKED_UP="(RESETLOGS_ID='${resetlogs_id}') and (deleted = 'NO') \
    and (ARCHIVED='YES') and name like '$ARCHIVE/resetlogs_id${resetlogs_id}/arch_%'\
    and next_change# > ${from_scn}"

    echo "" > "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range.bak
    for node in ${node_list}; do
        scnRange=`get_archiveLog_scn_range_by_thread "${instance_name}" "${db_user}" "${db_password}" "${node}"`
        log_info "resetlogs_id=${resetlogs_id}, node=${node}, new scnRange=${scnRange}"
        if [ -z "${scnRange}" ]; then
            continue
        fi
        echo "node${node}=${scnRange}" >> "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range.bak
    done
    mv "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range.bak "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range
}

calintersection_log_range()
{
    before_range=$1
    after_range=$2
    c_result_range=
    for wordA in ${before_range}; do
        before_min_scn=`echo "$wordA" | ${MYAWK} -F "~" '{print $1}'`
        before_min_time=`echo "$wordA" | ${MYAWK} -F "~" '{print $2}'`
        before_max_scn=`echo "$wordA" | ${MYAWK} -F "~" '{print $3}'`
        before_max_time=`echo "$wordA" | ${MYAWK} -F "~" '{print $4}'`
        for wordB in ${after_range}; do
            after_min_scn=`echo "$wordB" | ${MYAWK} -F "~" '{print $1}'`
            after_min_time=`echo "$wordB" | ${MYAWK} -F "~" '{print $2}'`
            after_max_scn=`echo "$wordB" | ${MYAWK} -F "~" '{print $3}'`
            after_max_time=`echo "$wordB" | ${MYAWK} -F "~" '{print $4}'`
            if [ "${before_min_scn}" -gt "${after_max_scn}" ] || [ "${after_min_scn}" -gt "${before_max_scn}" ]; then
                continue
            fi

            if [ "${before_min_scn}" -gt "${after_min_scn}" ]; then
                c_result_range="${c_result_range} ${before_min_scn}~${before_min_time}"
            else
                c_result_range="${c_result_range} ${after_min_scn}~${after_min_time}"
            fi
            if [ "${before_max_scn}" -gt "${after_max_scn}" ]; then
                c_result_range="${c_result_range}~${after_max_scn}~${after_max_time}"
            else
                c_result_range="${c_result_range}~${before_max_scn}~${before_max_time}"
            fi
        done
    done
    echo "${c_result_range}"
}

get_archiveLog_scn_range_by_thread()
{
    instance_name=$1
    db_user=$2
    db_password=$3
    thread=$4

    tmp_sql="${STMP_PATH}/TrunCateLog${PID}.sql"
    tmp_rst="${STMP_PATH}/TrunCateLogRST${PID}.txt"

    echo "set linesize 300;" > "${tmp_sql}"
    echo "col to_char(min(first_change#)) for a30;" >> "${tmp_sql}"
    echo "col to_char(max(next_change#)) for a30;" >> "${tmp_sql}"
    echo "select min(sequence#), max(sequence#), to_char(min(first_change#)), to_char(max(next_change#)), to_char(min(first_time), $TIME_FORMAT),\
    to_char(max(next_time), $TIME_FORMAT) from v\$archived_log where ${LOG_IS_BACKED_UP} and THREAD#=${thread};" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"

    oracle_exe_sql "${db_user}" "${db_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 30 1
    ret_code=$?
    if [ ${ret_code} -ne 0 ]; then
        log_error "Get archived log range failed ret_code=$ret_code"
        delete_file "${tmp_sql}" "${tmp_rst}" "true"
        exit $ret_code
    fi
    min_sequence=`cat "${tmp_rst}" | $MYAWK '{print $1}'`
    max_sequence=`cat "${tmp_rst}" | $MYAWK '{print $2}'`
    min_scn=`cat "${tmp_rst}" | $MYAWK '{print $3}'`
    max_scn=`cat "${tmp_rst}" | $MYAWK '{print $4}'`
    min_time=`cat "${tmp_rst}" | $MYAWK '{print $5}'`
    max_time=`cat "${tmp_rst}" | $MYAWK '{print $6}'`
    log_info "Node [$thread] sequenceRange=${min_sequence}~${max_sequence}, scnRange=${min_scn}~${max_scn}, timeRange=${min_time}~${max_time}"

    echo "set linesize 600;" > "${tmp_sql}"
    echo "select * from ( select to_char(t.next_change#) minscn, to_char(lead(t.first_change#,1,0) over(order by t.sequence#)) maxscn, \
    t.next mintime, to_char(lead(t.first,1,0) over(order by t.sequence#)) maxtime, \
    to_char(lead(t.sequence#,1,0) over(order by t.sequence#)-t.sequence#-1) bb \
    from (select sequence#, first_change#, next_change#, \
    to_char(first_time, 'YYYY-MM-DD_HH24:MI:SS') first, to_char(next_time, 'YYYY-MM-DD_HH24:MI:SS') next \
    from v\$archived_log where ${LOG_IS_BACKED_UP} and THREAD#=${thread})t ) where bb>0;" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"

    oracle_exe_sql "${db_user}" "${db_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 30 1
    ret_code=$?
    if [ ${ret_code} -ne 0 ]; then
        log_error "Get archived log range info failed ret_code=$ret_code"
        delete_file "${tmp_sql}" "${tmp_rst}" "true"
        exit $ret_code
    fi
    missingNum=`cat "${tmp_rst}" | wc -l | $MYAWK '{print $1}'`
    log_info "missingNum=${missingNum}"

    if [ "${missingNum}" -eq 0 ]; then
        scnRange="${min_scn}~${min_time}~${max_scn}~${max_time}"
    else
        scnRange="${min_scn}~${min_time}"
        index=1
        while "$index" -le "${missingNum}"; do
            t_begin="`cat "${tmp_rst}" | sed -n "${index}p" | $MYAWK '{print $1}'`~`cat "${tmp_rst}" | sed -n "${index}p" | $MYAWK '{print $3}'`"
            t_end="`cat "${tmp_rst}" | sed -n "${index}p" | $MYAWK '{print $2}'`~`cat "${tmp_rst}" | sed -n "${index}p" | $MYAWK '{print $4}'`"
            if [ "${index}" -eq "${missingNum}" ]; then
                scnRange="${scnRange}~${t_begin} ${t_end}~${max_scn}~${max_time}"
            else
                scnRange="${scnRange}~${t_begin} ${t_end}"
            fi
            index=`expr "$index" + 1`
        done
    fi

    db_role=`get_db_role "${instance_name}" "${db_user}" "${db_password}"`
    if [ "${db_role}" = 'PRIMARY' ]; then
      if [ "${min_sequence}" -eq "${max_sequence}" ]; then
          scnRange=
      fi
    fi

    echo "${scnRange}"
}

get_all_archivelog_range()
{
    log_info "Begin GetArchiveLogRange"
    logbackuprst="logbackuprst"

    for dirName in `ls -F "${ARCHIVE}" | grep "/$" | grep resetlogs_id*`; do
        resetlogs_id=`rd_substr "${dirName%?}" 13`
        rm -rf "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range.bak
        if [ ! -f "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range ] || [ ! -s "${ARCHIVE}"/resetlogs_id"${resetlogs_id}"/log_range ]; then
            continue
        fi
        result_range=
        while read line; do
            node=`echo "${line}" | ${MYAWK} -F "=" '{print $1}'`
            scn_range=`echo "${line}" | ${MYAWK} -F "=" '{print $2}'`
            if [ -z "${scn_range}" ]; then
                continue
            fi
            if [ -z "${result_range}" ]; then
                result_range=${scn_range}
            else
                result_range=`calintersection_log_range "${result_range}" "${scn_range}"`
            fi
        done < "${ARCHIVE}/resetlogs_id${resetlogs_id}/log_range"

        log_info "Get result_range=${result_range}."
        for word in ${result_range}; do
            min_scn=`echo "$word" | ${MYAWK} -F "~" '{print $1}'`
            min_time=`echo "$word" | ${MYAWK} -F "~" '{print $2}'`
            min_timestamp=`add_unix_timestamp "$min_time"`
            min_timestamp=$(echo "${min_timestamp}" | tail -n 1)
            max_scn=`echo "$word" | ${MYAWK} -F "~" '{print $3}'`
            max_time=`echo "$word" | ${MYAWK} -F "~" '{print $4}'`
            max_timestamp=`add_unix_timestamp "$max_time"`
            max_timestamp=$(echo "${max_timestamp}" | tail -n 1)
            logbackuprst="${logbackuprst};${min_scn};${min_timestamp};${max_scn};${max_timestamp};${resetlogs_id}"
        done
    done

    log_info "logbackuprst=$logbackuprst."
    echo "${logbackuprst}"
}

get_last_log_scn()
{
    instance_name=$1
    db_user=$2
    db_password=$3
    thread=$4

    tmp_sql="${STMP_PATH}/get_last_log_scn_sql_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_last_log_scn_rst_${PID}.txt"

    echo "set linesize 300;" > "$tmp_sql"
    if [ ! -z "${thread}" ];then
      echo "select to_char(max(next_change#)) from v\$archived_log where $LOG_IS_BACKED_UP and THREAD#=${thread};" >> "$tmp_sql"
    else
      echo "select to_char(max(next_change#)) from v\$archived_log where $LOG_IS_BACKED_UP;" >> "$tmp_sql"
    fi

    echo "exit" >> "$tmp_sql"
    set_db_silence_sql "${tmp_sql}"

    oracle_exe_sql "${db_user}" "${db_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 30 1
    ret_code=$?
    if [ ${ret_code} -ne 0 ]; then
        log_error "Exec SQL to Get From Scn failed,ret=${ret_code}"
        delete_file "${tmp_sql}" "${tmp_rst}" "true"
        return $ret_code
    fi
    from_scn=`cat "${tmp_rst}" | tr -d "\t" | tr -d " "`
    delete_file "${tmp_sql}" "${tmp_rst}" "true"
    log_info "Get last archive log scn  is $from_scn."

    get_log_time_sql="${STMP_PATH}/get_log_time_sql_${PID}.sql"
    get_log_time_rst="${STMP_PATH}/get_log_time_rst_${PID}.txt"
    echo "set linesize 300;" > "${get_log_time_sql}"
    if [ ! -z "${thread}" ];then
      echo "select to_char(next_time, $TIME_FORMAT) from v\$archived_log where NEXT_CHANGE#=${from_scn} and $LOG_IS_BACKED_UP and THREAD#=${thread};" >> "${get_log_time_sql}"
    else
      echo "select to_char(next_time, $TIME_FORMAT) from v\$archived_log where NEXT_CHANGE#=${from_scn} and $LOG_IS_BACKED_UP;" >> "${get_log_time_sql}"
    fi
    echo "exit" >> "${get_log_time_sql}"
    set_db_silence_sql "${get_log_time_sql}"
    oracle_exe_sql "${db_user}" "${db_password}" "${get_log_time_sql}" "${get_log_time_rst}" "${instance_name}" 30 1
    ret_code=$?
    if [ ${ret_code} -ne 0 ]; then
        log_error "Exec SQL to Get NEXT_TIME failed,ret=${ret_code}"
        delete_file "${get_log_time_sql}" "${get_log_time_rst}" "true"
        return $ret_code
    fi
    log_timestamp=`cat "${get_log_time_rst}" | tr -d "\t" | tr -d " "`
    delete_file "${get_log_time_sql}" "${get_log_time_rst}" "true"
    log_info "Get last archive log time is $log_timestamp."

    echo "$from_scn $log_timestamp"
}

get_min_of_last_log_scn_by_thread()
{
    # RAC场景下有多个thread，此函数获取每个thread日志scn中最小的那个scn
    instance_name=$1
    db_user=$2
    db_password=$3
    database_role=`get_db_role "${instance_name}" "${db_user}" "${db_password}"`
    if [ "${database_role}" = "PRIMARY" ]; then
      node_list=`get_database_node_list "${instance_name}" "${db_user}" "${db_password}"`
    else
      node_list=`get_database_node_list_standby "${instance_name}" "${db_user}" "${db_password}"`
    fi
    log_info "database_node_list ${node_list}"
    ret_code=$?
    if [ $ret_code -ne 0 ]; then
        log_error "Get node list failed ret code $ret_code"
        return $ret_code
    fi
    # oracle 19c的scn最大值是2^48，此处取一个比它大的值
    scn_min="300000000000000"
    min_scn_log_info=""
    for node in ${node_list}; do
        last_log_info=`get_last_log_scn "${instance_name}" "${db_user}" "${db_password}" "${node}"`
        from_scn=`echo "$last_log_info" | ${MYAWK} '{print $1}'`
        if [ "${from_scn}" -lt "${scn_min}" ];then
          scn_min="${from_scn}"
          min_scn_log_info="$last_log_info"
        fi
    done
    log_info "min_scn_log_info ${min_scn_log_info}"
    echo "${min_scn_log_info}"
}

change_archive_log_unavailable()
{
    type=$1
    change_archive_log_unavailable_sql="${STMP_PATH}/change_archive_log_unavailable_sql_${PID}.sql"
    change_archive_log_unavailable_rst="${STMP_PATH}/change_archive_log_unavailable_rst_${PID}.txt"
    if [ "${type}" = "log" ];then
      # linux
      echo "change archivelog like '/mnt/databackup/Oracle%/log/Database_%_LogRepository%/resetlogs_id%/arch_%.log' unavailable;" >> "${change_archive_log_unavailable_sql}"
      # aix
      echo "change archivelog like '/mnt/databackup/oracle_log%/resetlogs_id%/arch_%.log' unavailable;" >> "${change_archive_log_unavailable_sql}"
    else
      # linux/aix
      echo "change archivelog like '/mnt/databackup/oracle_backup_data_path%/log/arch_%.log' unavailable;" >> "${change_archive_log_unavailable_sql}"
    fi
    chown "${ORA_DB_USER}" "${change_archive_log_unavailable_sql}"
    chmod 700 "${change_archive_log_unavailable_sql}"
    log_info "Running RMAN to change backup archive logs unavailable"
    rman_exe_script "${DBUSER}" "${DBUSERPWD}" "${change_archive_log_unavailable_sql}" "${change_archive_log_unavailable_rst}" "${DBINSTANCE}" -1 1
    ret_code=$?
    if [ ${ret_code} -ne 0 ]; then
        log_error "Change database-${DBINSTANCE} log unavailable failed  code is ${ret_code}"
        delete_file "${change_archive_log_unavailable_sql}" "${change_archive_log_unavailable_rst}" "true"
        if [ "${ERROR_SCRIPT_EXEC_FAILED}" = "${ret_code}" ]; then
            exit "${ERROR_ORACLE_BACKUP_FAILED}"
        fi
        exit ${ret_code}
    fi

    log_info "Change database-${DBINSTANCE} log unavailable succ."
    delete_file "${change_archive_log_unavailable_sql}" "${change_archive_log_unavailable_rst}" "true"
}


check_standby_database_sequence()
{
    instance_name=$1
    db_user=$2
    db_password=$3

    database_role=`get_db_role "${instance_name}" "${db_user}" "${db_password}"`
    if [ "${database_role}" != "PRIMARY" ]; then
        # 登录数据库获取db_name和db_unique_name
        name_list=`get_db_name_and_db_unique_name "${instance_name}" "${db_user}" "${db_password}"`
        if [ $? -ne 0 ] || [ -z "$name_list" ]; then
            log_error "Get db name and db unique name failed"
            exit ${ERROR_PRIMARY_STANDBY_DIFFERENT}
        fi
        db_name=`echo "${name_list}" | $MYAWK '{print $1}'`
        db_unique_name=`echo "${name_list}" | $MYAWK '{print $2}'`

        # 获取备库最大sequence值
        standby_max_sequence=`get_standby_max_sequence "${instance_name}" "${db_user}" "${db_password}"`

        # 打开tnsnames.ora，获取connect identifier
        identifiers=`get_connect_identifier`
        log_info "identifiers: $identifiers"
        # 遍历登录identifier，找到数据库名一致，unique_name不一致的
        correct_identifier=`find_right_identifier "${instance_name}" "${db_user}" "${db_password}" "${identifiers}" "${db_name}" "${db_unique_name}"`
        log_info "correct_identifier:$correct_identifier"
        if [ $? -ne 0 ] || [ -z "$correct_identifier" ]; then
            log_error "find correct identifier FAILED!"
            exit ${ERROR_PRIMARY_STANDBY_DIFFERENT}
        fi

        # 登录到这个identifier，获取其最大sequence
        primary_max_sequence=`get_primary_max_sequence "${instance_name}" "${db_user}" "${db_password}" "${correct_identifier}"`
        if [ $? -ne 0 ] || [ -z "$name_list" ]; then
            log_error "Get max sequence of primary database FAILED!"
            exit ${ERROR_PRIMARY_STANDBY_DIFFERENT}
        fi

        # 对比主备库的最大sequence
        if [ "${primary_max_sequence}" -gt "${standby_max_sequence}" ]; then
            log_error "The max sequence of primary is larger than standby!"
            return ${ERROR_PRIMARY_STANDBY_DIFFERENT}
        fi
    fi
    # 否则返回成功
    log_info "The sequences are consistent!"
    return 0
}

get_db_name_and_db_unique_name()
{
    instance_name=$1
    database_user=$2
    database_password=$3

    tmp_sql="${STMP_PATH}/get_db_name_sql_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_db_name_rst_${PID}.txt"
    echo "set linesize 999;" > "${tmp_sql}"
    echo "select name, db_unique_name from v\$database;" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"
    oracle_exe_sql "${database_user}" "${database_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 10 1
    if [ ${ret_code} -ne 0 ]; then
        log_info "Get database name and unique name failed, ret=${ret_code}, error is `cat "${tmp_rst}"`"
        delete_file "${tmp_sql}" "${tmp_rst}"
        return ${ret_code}
    fi
    cat "${tmp_rst}"
    delete_file "${tmp_sql}" "${tmp_rst}"
}


get_connect_identifier()
{
    tmp_sql="${STMP_PATH}/get_connect_identifier_${PID}.txt"
    tmp_rst="${STMP_PATH}/get_connect_identifier_${PID}_rst.txt"
    su - "${ORA_DB_USER}" "${ORACLE_SHELL_MODE}" -c "echo \"${ORACLE_MARK}\"; ${EXPORT_ORACLE_ENV} cat \$ORACLE_HOME/network/admin/tnsnames.ora" > "${tmp_sql}" 2>&1
    delete_oracle_mark "${tmp_sql}"
    while read line; do
      first_char=$(echo $line | cut -c1)
      if [ -n "$line" ] && [ "${first_char}" != "#" ] && [ "${first_char}" != "(" ] && [ "${first_char}" != ")" ]; then
          echo "$line" | cut -d= -f1 >> $tmp_rst
      fi
    done < "${tmp_sql}"

    while read line; do
        if [ -z "$result" ]; then
        result="$line"
        else
          result="$result $line"
        fi
    done < "${tmp_rst}"

    delete_file "${tmp_sql}" "${tmp_rst}" "true"
    echo "${result}"
}


find_right_identifier()
{
    instance_name=$1
    database_user=$2
    database_password=$3
    identifiers=$4
    target_db_name=$5
    target_db_unique_name=$6

    log_info "target_db_unique_name: $target_db_unique_name, target_db_name:$target_db_name"
    tmp_sql="${STMP_PATH}/get_right_identifier_sql_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_right_identifier_rst_${PID}.txt"
    echo "set linesize 999;" > "${tmp_sql}"
    echo "select name, db_unique_name, database_role from v\$database;" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"
    for curr_identifier in $identifiers
    do
        log_info "curr_identifier: $curr_identifier"
        oracle_exe_sql_for_exact_identifier "${database_user}" "${database_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" "${curr_identifier}" 10 1
        if [ "$RET_CODE" -ne "0" ]; then
            delete_file "${tmp_sql}" "${tmp_rst}" "true"
            log_info "Login to the specific identifier failed."
            return ${ret_code}
        fi
        curr_db_name=`cat "${tmp_rst}" | ${MYAWK} '{print $1}'`
        curr_db_unique_name=`cat "${tmp_rst}" | ${MYAWK} '{print $2}'`
        curr_db_role=`cat "${tmp_rst}" | ${MYAWK} '{print $3}'`
        if [ "${curr_db_role}" == "PRIMARY" ] && [ "${curr_db_name}" == "${target_db_name}" ] && [ "${curr_db_unique_name}" != "${target_db_unique_name}" ]; then
            delete_file "${tmp_sql}" "${tmp_rst}" "true"
            log_info "Find the correct identifier!"
            echo ${curr_identifier}
            return ${ret_code}
        fi
    done
    delete_file "${tmp_sql}" "${tmp_rst}" "true"
}


get_primary_max_sequence()
{
    instance_name=$1
    database_user=$2
    database_password=$3
    correct_identifier=$4

    tmp_sql="${STMP_PATH}/get_primary_max_sequence_sql_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_primary_max_sequence_rst_${PID}.txt"
    echo "set linesize 999;" > "${tmp_sql}"
    echo "select max(sequence#) from v\$archived_log;" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"
    oracle_exe_sql_for_exact_identifier "${database_user}" "${database_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" "${correct_identifier}" 10 1
    if [ ${ret_code} -ne 0 ]; then
        log_info "Get database name and unique name failed, ret=${ret_code}, error is `cat "${tmp_rst}"`"
        delete_file "${tmp_sql}" "${tmp_rst}"
        return ${ret_code}
    fi
    primary_max_sequence=`cat "${tmp_rst}" | $MYAWK '{print $NF}'`
    log_info "The primary_max_sequence = ${primary_max_sequence}."
    delete_file "${tmp_sql}" "${tmp_rst}"
    echo "${primary_max_sequence}"
}


get_standby_max_sequence()
{
    instance_name=$1
    database_user=$2
    database_password=$3

    tmp_sql="${STMP_PATH}/get_standby_max_sequence_sql_${PID}.sql"
    tmp_rst="${STMP_PATH}/get_standby_max_sequence_rst_${PID}.txt"
    echo "set linesize 999;" > "${tmp_sql}"
    echo "select max(sequence#) from v\$archived_log;" >> "${tmp_sql}"
    echo "exit" >> "${tmp_sql}"
    set_db_silence_sql "${tmp_sql}"
    oracle_exe_sql "${database_user}" "${database_password}" "${tmp_sql}" "${tmp_rst}" "${instance_name}" 10 1
    if [ ${ret_code} -ne 0 ]; then
        log_info "Get database name and unique name failed, ret=${ret_code}, error is `cat "${tmp_rst}"`"
        delete_file "${tmp_sql}" "${tmp_rst}"
        return ${ret_code}
    fi
    standby_max_sequence=`cat "${tmp_rst}" | $MYAWK '{print $NF}'`
    log_info "The standby_max_sequence = ${standby_max_sequence}."
    delete_file "${tmp_sql}" "${tmp_rst}"
    echo "${standby_max_sequence}"
}



oracle_exe_sql_for_exact_identifier()
{
    db_user=$1
    db_pwd=$2
    # only oracle can modify sql_file file
    sql_file=$3
    # only root can modify rst file
    rst_file=$4
    instance_name=$5
    curr_identifier=$6
    # execute sql timeout -1 is not limit
    timeout_second=$7
    # 静默模式只显示结果信息
    is_silence=$8
    if [ "${is_silence}" -eq 1 ]; then
        silent_mode="-S"
    fi
    sqlplus_timeout "${timeout_second}"
    # 提取到外面进行 函数呢不做修改文件权限 oracle必须要有权限读取权限

    set_env_cmd=`generate_export_oracle_sid "${instance_name}"`
    su - "${ORA_DB_USER}" "${ORACLE_SHELL_MODE}" -c "echo \"${ORACLE_MARK}\"; ${EXPORT_ORACLE_ENV} ${set_env_cmd} sqlplus ${silent_mode} ${db_user}'/'${db_pwd}'@'${curr_identifier} 'as sysdba' @'${sql_file}' " >"${rst_file}" 2>&1
    result_code=$?
    log_info "result_code:$result_code"
    delete_oracle_mark "${rst_file}"
    # ~/.bash_profile 配置了stty erase ^H 时执行sql会出现stty:开头的内容
    if [ ${result_code} -eq 0 ]; then
      sed -i '/^stty:/d' "${rst_file}"
    fi
    if [ ${result_code} -ne 0 ]; then
        log_error "Execute oracle sqlplus cmd failed, cmd ret code ${result_code}"
        result_code=$ERROR_SCRIPT_EXEC_FAILED
    fi
    get_sql_error_info "${rst_file}" "$result_code" 1
    return $?
}